﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using SelfMadeMillionaire.Core;
using SelfMadeMillionaire.Core.Entities;
using SelfMadeMillionaire.Core.Enums;
using SelfMadeMillionaire.Core.Repositories;
using SelfMadeMillionaire.Core.Services;

namespace SelfMadeMillionaire.BLL.Services
{
	public class SubscriberService : ISubscriberService
	{
		private readonly IOrderRepository _orderRepository;
		private readonly IPayPalPaymentRepository _payPalPaymentRepository;
        private readonly IPayPalTransactionRepository _payPalTransactionRepository;
        private readonly IPayPalBuyerRepository _payPalBuyerRepository;
		private readonly ISubscriberRepository _subscriberRepository;

		public SubscriberService(ISubscriberRepository subscriberRepository, IOrderRepository orderRepository,
            IPayPalPaymentRepository payPalPaymentRepository, IPayPalTransactionRepository payPalTransactionRepository,
            IPayPalBuyerRepository payPalBuyerRepository)
		{
			_orderRepository = orderRepository;
			_subscriberRepository = subscriberRepository;
			_payPalPaymentRepository = payPalPaymentRepository;
            _payPalTransactionRepository = payPalTransactionRepository;
            _payPalBuyerRepository = payPalBuyerRepository;
		}

        public SubscriberService(ISubscriberRepository subscriberRepository)
        {
            _subscriberRepository = subscriberRepository;
        }

		public bool IsExists(string email, int id)
		{
			IEnumerable<Subscriber> subscribers = _subscriberRepository.GetAll();
			return subscribers.Any(x => x.Email.ToLower() == email.ToLower() && x.ID != id);
		}

		public void Add(Subscriber subscriber)
		{
			subscriber.CreatedDate = DateTime.Now;
			subscriber.StartDate = DateTime.Now;
			subscriber.EndDate = DateTime.Now.AddYears(1);

			_subscriberRepository.Add(subscriber);
		}

		public Subscriber Get(int id)
		{
			return _subscriberRepository.Get(id);
		}

		public Subscriber Get(Guid token)
		{
			return _subscriberRepository.Get(token);
		}

        public Subscriber GetByAuthToken(string token)
        {
            return _subscriberRepository.GetAll(x => x.TokenApi == token).SingleOrDefault();
        }

        public Subscriber GetByPasswordRecoveryKey(Guid key)
        {
            return _subscriberRepository.GetByPasswordRecoveryKey(key);
        }

        public IEnumerable<Subscriber> GetByDeviceKey(string key)
        {
            return _subscriberRepository.GetAll(x => x.DeviceKey == key);
        }
        
		public void Activate(Subscriber subscriber)
		{
			subscriber.Status = (byte) StatusType.Active;
			Update(subscriber);
		}

        public void CreatePasswordRecovery(Subscriber subscriber)
        {
            subscriber.PasswordRecoveryKey = Guid.NewGuid();
            Update(subscriber);
        }

        public void Update(Subscriber subscriber)
		{
			_subscriberRepository.Update(subscriber);
		}


		public void Remove(int id)
		{
			Subscriber subscriber = Get(id);
			subscriber.Status = (byte) StatusType.Deleted;
			subscriber.Email = subscriber.Email + "_removed_" + DateTime.Now.ToString("yyyy.MM.dd.hh-mm-ss");

			_subscriberRepository.Commit();
		}


		public void SubscribeSMMFree(Subscriber subscriber)
		{
			subscriber.SubscriptionLevel = (byte) SubscriptionType.Free;
            subscriber.SmmUnsubscribed = false;
			_subscriberRepository.Commit();
		}

        /// <summary>
        /// Unsubscribe from SMM
        /// </summary>
		public void Unsubscribe(int id)
		{
			var subscriber = Get(id);
	        subscriber.SubscriptionLevel = (byte)SubscriptionType.Free;
            subscriber.SmmUnsubscribed = true;
			_subscriberRepository.Commit();
		}


		public void SubscribeMMM(Subscriber subscriber)
		{
            subscriber.MmmUnsubscribed = false;
			_subscriberRepository.Commit();
		}

        /// <summary>
        /// Unsubscribe from MMM
        /// </summary>
		public void UnsubscribeMMM(Subscriber subscriber)
		{
			subscriber.MmmUnsubscribed = true;
			_subscriberRepository.Commit();
		}

        public void Unsubscribe(Subscriber subscriber)
        {
            subscriber.SmmUnsubscribed = true;
            subscriber.MmmUnsubscribed = true;
            Update(subscriber);

            //_subscriberRepository.UnsubscribeSP(subscriber.Email);
        }

        public void UnsubscribeSP(string email)
        {
            _subscriberRepository.UnsubscribeSP(email);
        }

		public Subscriber Get(string email)
		{
			return _subscriberRepository.Get(email);
		}


		public SubscriptionType GetMinAccessLevel(string email)
		{
			if (email == "admin")
				return SubscriptionType.Platinum;

			Subscriber subscriber = Get(email);

			if ((subscriber == null) || (subscriber.SubscriptionLevel == null) || !subscriber.IsSMMSubscriber())
				return SubscriptionType.Free;

			return (SubscriptionType) subscriber.SubscriptionLevel.Value;
		}

		public Order Order(string subscriberEmail,byte productId)
		{
			Subscriber subscriber = Get(subscriberEmail);

			Order order = GetOrder(subscriber.Email);


			var price = GetProductPrice(subscriber, productId);
			var productType = ProductType.SelfMadeMillionaire;
			var subscriptionLevel = GetProductSubsciptionLevel(subscriber, productId);
			var expirationDate = GetProductExpirationDate(subscriber, productId);

			if (order == null)
			{
				order = new Order
				{
					Price = price,
					ProductId =  productId,
					Status = (byte)StatusType.Active,
					ProductType = (byte)productType,
					SubscriptionLevel = (byte)subscriptionLevel,
					CreatedDate = DateTime.Now,
					Subscriber = subscriber,
					ExperationDate = expirationDate
				};

				_orderRepository.Add(order);
			}
			else
			{
				order = GetOrder(subscriber.Email);
				order.ExperationDate = expirationDate;
				order.SubscriptionLevel = (byte)subscriptionLevel;
				order.ProductType = (byte)productType;
				order.Price = price;
				order.ProductId = productId;

				_orderRepository.Update(order);
			}

            _orderRepository.Commit();

            return order;
		}


        public void OrderWithPayment(string subscriberEmail, byte productId)
        {
            Subscriber subscriber = Get(subscriberEmail);

            Order order = GetOrder(subscriber.Email);


            var price = GetProductPrice(subscriber, productId);
            var productType = ProductType.SelfMadeMillionaire;
            var subscriptionLevel = GetProductSubsciptionLevel(subscriber, productId);
            var expirationDate = GetProductExpirationDate(subscriber, productId);

            if (order == null)
            {
                order = new Order
                {
                    Price = price,
                    ProductId = productId,
                    Status = (byte)StatusType.Paid,
                    ProductType = (byte)productType,
                    SubscriptionLevel = (byte)subscriptionLevel,
                    CreatedDate = DateTime.Now,
                    Subscriber = subscriber,
                    ExperationDate = expirationDate
                };

                _orderRepository.Add(order);
            }
            else
            {
                order = GetOrder(subscriber.Email);
                order.ExperationDate = expirationDate;
                order.Status = (byte)StatusType.Paid;
                order.SubscriptionLevel = (byte)subscriptionLevel;
                order.ProductType = (byte)productType;
                order.Price = price;
                order.ProductId = productId;

                _orderRepository.Update(order);
            }

            _orderRepository.Commit();

            subscriber.SubscriptionLevel = order.SubscriptionLevel;
            subscriber.EndDate = order.ExperationDate;
            subscriber.SmmUnsubscribed = false;

            order = GetLastPaidOrder(subscriber.Email);
            _payPalPaymentRepository.Add(new PayPalPayment()
            {
                Custom = order.ID.ToString(),
                HandlingAmount = 0,
                ItemName = GetProducts(subscriber).Where(p => p.Id == productId).First().ProductName,
                ItemNumber = subscriptionLevel.ToString(),
                McCurrency = "USD",
                McFee = 0,
                McGross = order.Price,
                PaymentDate = DateTime.Now.ToString(),
                PaymentFee = 0,
                PaymentGross = 0,
                PaymentStatus = "Completed",
                PaymentType = "instant",
                Quantity = 1,
                Shipping = 0,
                Tax = 0,
                Status = (byte)StatusType.Paid,
                Order = order
            });
            _payPalPaymentRepository.Commit();
            var pp = _payPalPaymentRepository.GetAll().OrderByDescending(p => p.ID).Where(p => p.Order.ID == order.ID).Single();

            var g = Guid.NewGuid().ToString();
            _payPalTransactionRepository.Add(new PayPalTransaction()
            {
                Buyer = _payPalBuyerRepository.GetAll().First(),
                Payment = pp,
                Status = (byte)StatusType.Paid,
                txnId = g,
                txnType = "web_accept",
                verifySign = g
            });
            _payPalTransactionRepository.Commit();
        }

		public Order GetOrder(string subscriberEmail)
		{
			return
				_orderRepository.GetAll()
					.FirstOrDefault(x => x.Subscriber != null && x.Subscriber.Email == subscriberEmail && x.Status == (byte) StatusType.Active);
		}

        Order GetLastPaidOrder(string subscriberEmail)
        {
            return _orderRepository.GetAll()
                .OrderByDescending(o => o.ID)
                .Where(x => x.Subscriber != null && x.Subscriber.Email == subscriberEmail && x.Status == (byte)StatusType.Paid)
                .FirstOrDefault();
        }

        public Order UpdateOrder(Order order)
        {
            return _orderRepository.Update(order);
        }

        public Order GetOrderBySubscriberAndPromoCode(string subscriberEmail, string promocode)
        {
            return
                _orderRepository.GetAll()
                    .FirstOrDefault(x => x.Subscriber.Email == subscriberEmail && x.PromoCode == promocode && x.Status == (byte)StatusType.Paid);
        }

		public void Cancel(string subscriberEmail)
		{
			Order order = GetOrder(subscriberEmail);

			if (order == null)
				return;

			order.Status = (byte) StatusType.Canceled;
			_orderRepository.Commit();
		}

		public double GetUnusedMoney(string subscriberEmail)
		{
		
			// сначала ищем последний оплаченный ордер подписчика
			// если такового нет, значит неиспользованных денег тоже нет
			Order lastPaidOrder =
				_orderRepository.GetAll()
					.Where(x => x.Subscriber != null && x.Subscriber.Email == subscriberEmail && x.Status == (byte) StatusType.Paid)
					.OrderByDescending(o => o.ID)
					.FirstOrDefault();
			if (lastPaidOrder == null) return 0;

			// возможно ордер уже просрочен
			if (DateTime.Now > lastPaidOrder.ExperationDate) return 0;


			// получаем сумму оплат по данному ордеру
			double totalPaid =
				_payPalPaymentRepository.GetAll().Where(x => x.Custom == lastPaidOrder.ID.ToString()).Sum(x => x.McGross);

			// на сколько дней оформлена подписка
			int totalDays = (lastPaidOrder.ExperationDate - lastPaidOrder.CreatedDate).Days;

			// сколько стоит один день подписки
			double dayCost = totalPaid/totalDays;

			// сколько дней подписки еще осталось
			int daysLeft = (lastPaidOrder.ExperationDate - DateTime.Now).Days;

			
			// возвращаем стоимость оставшихся дней подписки
			return daysLeft*dayCost;
		}

		Product[] ISubscriberService.GetProducts(Subscriber subscriber)
		{
			return GetProducts(subscriber);
		}


		public SubscriptionType GetProductSubsciptionLevel(Subscriber subscriber, int productId)
		{
			
			switch (productId)
			{
				case CoreConstants.UPGRADE_TO_GOLD_PRODUCT:
					return SubscriptionType.Gold;

				case CoreConstants.UPGRADE_TO_SILVER_PRODUCT:
					return SubscriptionType.Silver;

				case CoreConstants.RENEW_PRODUCT:
					return (SubscriptionType)subscriber.SubscriptionLevel;

                case CoreConstants.UPGRADE_TO_PLATINUM_PRODUCT:
                    return SubscriptionType.Platinum;
			}


			return SubscriptionType.Free;
		}



		private DateTime GetProductExpirationDate(Subscriber subscriber, byte productId)
		{
			switch (productId)
			{
				case CoreConstants.UPGRADE_TO_GOLD_PRODUCT:
				case CoreConstants.UPGRADE_TO_SILVER_PRODUCT:
                case CoreConstants.UPGRADE_TO_PLATINUM_PRODUCT:
					return subscriber.EndDate.HasValue ? subscriber.EndDate.Value : DateTime.Now.AddYears(1);
				case CoreConstants.RENEW_PRODUCT:
                    return subscriber.EndDate.HasValue ? subscriber.EndDate.Value.AddYears(1) : DateTime.Now.AddYears(1);
			}

			throw new NotImplementedException("Invalid productId passed");
		}


		public int GetProductPrice(Subscriber subscriber, int productId)
		{
			double baseprice = 0;

			switch (productId)
			{
				case CoreConstants.UPGRADE_TO_GOLD_PRODUCT:
			        baseprice = GetGoldPricePackage() - GetUnusedMoney(subscriber.Email);
					break;
				case CoreConstants.UPGRADE_TO_SILVER_PRODUCT:
                    baseprice = GetSilverPricePackage() - GetUnusedMoney(subscriber.Email);
                    break;
                case CoreConstants.UPGRADE_TO_PLATINUM_PRODUCT:
                    baseprice = GetPlatinumPricePackage() - GetUnusedMoney(subscriber.Email);
                    break;
				case CoreConstants.RENEW_PRODUCT:
					switch ((SubscriptionType) subscriber.SubscriptionLevel)
					{
						case SubscriptionType.Free:

							break;
						case SubscriptionType.Silver:
							baseprice = GetSilverPricePackage();
							break;
						case SubscriptionType.Gold:
							baseprice = GetGoldPricePackage();
							break;
                        case SubscriptionType.Platinum:
                            baseprice = GetPlatinumPricePackage();
                            break;
						default:
							throw new ArgumentOutOfRangeException();
					}
					break;
			}


            return (int)Math.Round(baseprice < 0 ? 0 : baseprice);
		}



        public double GetPlatinumPricePackage()
        {
            return double.Parse(ConfigurationManager.AppSettings[CoreConstants.PARAMETER_NAME_SUBSCRIPTION_PLATINUM_PRICE]);
        }

        public double GetGoldPricePackage()
		{
			return double.Parse(ConfigurationManager.AppSettings[CoreConstants.PARAMETER_NAME_SUBSCRIPTION_GOLD_PRICE]);
		}

        public double GetSilverPricePackage()
		{
			return double.Parse(ConfigurationManager.AppSettings[CoreConstants.PARAMETER_NAME_SUBSCRIPTION_SILVER_PRICE]);
		}


		protected Product[] GetProducts(Subscriber subscriber)
		{
			var producs = new List<Product>();
			producs.Add(

				new Product
				{
					Id = CoreConstants.FREE_SUBSCRIPTION,
					CommandName = string.Format("Upgrade to {0}", SubscriptionType.Free),
					ProductName = "Upgrade to Free subscription level for Self-Made Millionaire Newsletter",
					Cost = 0
				});
			producs.Add(
				new Product
				{
					Id = CoreConstants.UPGRADE_TO_SILVER_PRODUCT,
					CommandName =
						string.Format("Upgrade to {0} (${1}/year)", SubscriptionType.Silver,
							GetProductPrice(subscriber, CoreConstants.UPGRADE_TO_SILVER_PRODUCT)),
					ProductName = "Upgrade to Silver subscription level for Self-Made Millionaire Newsletter",
					Cost = GetProductPrice(subscriber, CoreConstants.UPGRADE_TO_SILVER_PRODUCT)
				});
			producs.Add(
				new Product
				{
					Id = CoreConstants.UPGRADE_TO_GOLD_PRODUCT,
					CommandName =
						string.Format("Upgrade to {0} (${1}/year)", SubscriptionType.Gold,
							GetProductPrice(subscriber, CoreConstants.UPGRADE_TO_GOLD_PRODUCT)),
					ProductName = "Upgrade to Gold subscription level for Self-Made Millionaire Newsletter",
					Cost = GetProductPrice(subscriber, CoreConstants.UPGRADE_TO_GOLD_PRODUCT)
				});
            producs.Add(
                new Product
                {
                    Id = CoreConstants.UPGRADE_TO_PLATINUM_PRODUCT,
                    CommandName =
                        string.Format("Upgrade to {0} (${1}/year)", SubscriptionType.Platinum,
                            GetProductPrice(subscriber, CoreConstants.UPGRADE_TO_PLATINUM_PRODUCT)),
                    ProductName = "Upgrade to Platinum subscription level for Self-Made Millionaire Newsletter",
                    Cost = GetProductPrice(subscriber, CoreConstants.UPGRADE_TO_PLATINUM_PRODUCT)
                });

            if (subscriber.SubscriptionLevel.HasValue && (subscriber.SubscriptionLevel > (byte)SubscriptionType.Free))
			{
				producs.Add(
					new Product
					{
						Id = CoreConstants.RENEW_PRODUCT,
						CommandName =
							string.Format("Renew {0} (${1})", (SubscriptionType) subscriber.SubscriptionLevel,
								GetProductPrice(subscriber, CoreConstants.RENEW_PRODUCT)),
						ProductName =
							string.Format("Renew {0} subscription level for Self-Made Millionaire Newsletter",
								(SubscriptionType) subscriber.SubscriptionLevel),
						Cost = GetProductPrice(subscriber, CoreConstants.RENEW_PRODUCT)
					});
			}

			return producs.ToArray();
        }

        #region Mailing
        public void UpdateMODetail_EmailOpen(string mailing)
        {
            mailing = EncryptDecrypt.ToDecrypt(mailing);
            _subscriberRepository.UpdateMODetail_EmailOpen(mailing);
        }

        public void UpdateMODetail_LandingOpen(string mailing)
        {
            mailing = EncryptDecrypt.ToDecrypt(mailing);
            _subscriberRepository.UpdateMODetail_LandingOpen(mailing);
        }

        public void UpdateMODetail_SubscribeRedirect(string mailing)
        {
            mailing = EncryptDecrypt.ToDecrypt(mailing);
            _subscriberRepository.UpdateMODetail_SubscribeRedirect(mailing);
        }

        public void UpdateMODetail_SubscribeFinish(string mailing)
        {
            mailing = EncryptDecrypt.ToDecrypt(mailing);
            _subscriberRepository.UpdateMODetail_SubscribeFinish(mailing);
        }
        #endregion
    }
}